package org.agnitas.cms.utils.preview;

import javax.imageio.*;
import javax.servlet.http.*;
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.beans.*;
import java.io.*;
import java.net.URL;
import java.util.concurrent.*;
import java.util.Map;
import java.util.Vector;

import org.agnitas.cms.utils.*;
import org.agnitas.cms.utils.dataaccess.*;
import org.agnitas.cms.web.*;
import org.agnitas.cms.webservices.generated.*;
import org.agnitas.util.*;
import org.agnitas.dao.RecipientDao;
import org.agnitas.dao.MailingDao;
import org.agnitas.dao.MailingComponentDao;
import org.agnitas.beans.MailingComponent;
import org.agnitas.beans.Mailing;
import org.apache.log4j.Logger;
import org.springframework.context.*;


/**
 * This class generate image from browser`s page.
 * Stores it image in database.
 */
public class PreviewImageGenerator {
	
	private static final transient Logger logger = Logger.getLogger( PreviewImageGenerator.class);

	JEditorPane editor;
	private boolean imagesLoaded = false;
	private boolean pageLoaded = false;
	private boolean rendered = false;
	private int initialWidth = 800;
	private int initialHeight = 600;
	private ApplicationContext aContext;
	private final HttpSession session;
	private int cmTemplateId;
	private int cmId;
	private int cmtId;
	private ThreadPoolExecutor threadPool;
	private int previewMaxWidth;
	private int previewMaxHeight;
    private static final int IMAGE_LOADING_TIMEOUT = 15;

    public PreviewImageGenerator(ApplicationContext applicationContext,
								 HttpSession session, final int previewMaxWidth,
								 final int previewMaxHeight) {

    	if( session == null) {
    		logger.error( "no session found for preview generation");
    		throw new NullPointerException( "SESSION is null");
    	}
    	
		aContext = applicationContext;
		this.session = session;
		final int nThreads = 0;
		final long keepAliveTime = 0;//seconds
        final int maxPoolThreads = 1;
		final LinkedBlockingQueue<Runnable> runnables =
				new LinkedBlockingQueue<Runnable>();
		threadPool = new ThreadPoolExecutor(nThreads, maxPoolThreads, keepAliveTime,
				TimeUnit.SECONDS, runnables);
		this.previewMaxWidth = previewMaxWidth;
		this.previewMaxHeight = previewMaxHeight;
	}

	/**
	 * Generate preview image for one of cms`s element id,
	 * element for wich generates preview must be non zero value and
	 * other two must be equals to zero.
	 *
	 * @param cmTemplateId id of cms`s template
	 * @param cmId		 id of content module
	 * @param cmtId		id of content module type
	 */
	public void generatePreview(int cmTemplateId, int cmId, int cmtId) {
		this.cmTemplateId = cmTemplateId;
		this.cmId = cmId;
		this.cmtId = cmtId;

		String previewUrl = generatePreviewUrl(cmTemplateId, cmId, cmtId);
		if(previewUrl == null) {
			return;
		}
		String systemUrl = AgnUtils.getEMMProperty("system.url");
		final String finalPreviewUrl = systemUrl + previewUrl;

		if( logger.isInfoEnabled())
			logger.info("HTML-preview URL is " + finalPreviewUrl);

		threadPool.execute(new Thread() {
			@Override
			public void run() {
				generatePreview(finalPreviewUrl, false);
			}
		});
	}

    public void generatePreview(final Integer mailingId, final Integer companyId, boolean isUseThread) {
        generatePreview(mailingId, companyId, isUseThread, false);
    }

    public void generatePreview(final Integer mailingId, final Integer companyId, boolean isUseThread, final boolean bulkGenerate) {
        pageLoaded = false;
        imagesLoaded = false;

        if (isUseThread) {
            final Thread command = new Thread() {
                @Override
                public void run() {
                	
                	try {
                		storeMailingPreview(mailingId, companyId, bulkGenerate);
                	} catch( NullPointerException e) {
                		logger.error( "Error generating preview", e);
                	}
                }
            };
            threadPool.execute(command);
        } else {
        	try {
        		storeMailingPreview(mailingId, companyId, bulkGenerate);
        	} catch( NullPointerException e) {
        		logger.error( "Error generating preview", e);
        	}
        }


    }

    private void storeMailingPreview(Integer mailingId, Integer companyId, boolean bulkGenerate) {
        RecipientDao recipientDao = (RecipientDao) aContext.getBean("RecipientDao");
        MailingDao mDao = (MailingDao) aContext.getBean("MailingDao");
        MailingComponentDao componentDao = (MailingComponentDao) aContext.getBean("MailingComponentDao");
        Mailing aMailing = (Mailing) mDao.getMailing(mailingId, companyId);
        final Map<Integer, String> testAdnAdminRecipients = recipientDao.getAdminAndTestRecipientsDescription(companyId, mailingId);
        if (!testAdnAdminRecipients.isEmpty()) {

            String previewUrl = "/mailingsend.do;jsessionid=" + session.getId() +
                    "?action=16&mailingID=" + mailingId + "&previewFormat=1&previewCustomerID="
                    + (Integer) testAdnAdminRecipients.keySet().toArray()[0] + "&previewDay=0&previewMonth=0&previewYear=0";

            String systemUrl = AgnUtils.getEMMProperty("system.url");
            final Vector<MailingComponent> mailingComponents = componentDao.getMailingComponents(mailingId, companyId, MailingComponent.TYPE_THUMBMAIL_IMAGE);
            if (bulkGenerate){
                previewUrl = previewUrl + "&previewCompanyId=" + companyId;
                session.setAttribute("bulkGenerate", "true");
            }
            else {
                session.setAttribute("bulkGenerate", null);
            }
            final String finalPreviewUrl = systemUrl + previewUrl;
            if (!mailingComponents.isEmpty()) {
                MailingComponent component = mailingComponents.get(0);
                component.setBinaryBlock(generatePreview(finalPreviewUrl, true));
                componentDao.saveMailingComponent(component);
            } else {

                MailingComponent component = (MailingComponent) aContext.getBean("MailingComponent");
                component.setCompanyID(companyId);
                component.setMailingID(mailingId);
                component.setType(MailingComponent.TYPE_THUMBMAIL_IMAGE);
                component.setDescription("Mailing preview Image");
                component.setComponentName("THUMBMAIL.png");
                component.setBinaryBlock(generatePreview(finalPreviewUrl, true));
                component.setMimeType("image/png");
                component.setEmmBlock(component.makeEMMBlock());
                aMailing.addComponent(component);
                mDao.saveMailing(aMailing);
            }
        }
    }

    byte[] generatePreview(String url,boolean isMailingPreview) {
    	if( logger.isInfoEnabled())
    		logger.info("Trying to set headless mode");
    	
		System.setProperty("java.awt.headless", "true");
		
		if( logger.isInfoEnabled())
			logger.info("Creating swing html editor");
		editor = new JEditorPane();
		CmsEditorKit editorKit = new CmsEditorKit() {
			@Override
			public void onImagesLoaded() {
				imagesLoaded = true;
				
				if( logger.isInfoEnabled())
					logger.info("preview`s image load finished");
			}
		};
		editor.setEditorKit(editorKit);
		editor.addPropertyChangeListener(new PropertyChangeListener() {
			public void propertyChange(PropertyChangeEvent event) {
				if("page".equals(event.getPropertyName())) {
					onPageLoaded();
				}
			}
		});

		try {
            URL page = new URL(url);

           if( "https".equalsIgnoreCase( page.getProtocol())) {
                URL realUrl = new URL(null, url, new TrustedHttpsHandler());
                editor.setPage(realUrl);
            } else {
                editor.setPage(url);
            }
		} catch(IOException e) {
			logger.error("URL for preview generation is not valid: " + url, e);
		}

        int secondCounter = 0;
        while (!(pageLoaded && imagesLoaded)) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
               logger.error("preview generation failed!!", e);
            }
            secondCounter++;
            if (secondCounter > IMAGE_LOADING_TIMEOUT) break;
        }

          return renderPreview(isMailingPreview);
	}

	private void onPageLoaded() {
		editor.setDoubleBuffered(false);
		editor.setSize(initialWidth, initialHeight);
		editor.addNotify();
		editor.validate();
		BufferedImage dummyImage =
				new BufferedImage(20, 20, BufferedImage.TYPE_INT_RGB);
		Graphics2D imageGraphics = dummyImage.createGraphics();
		editor.paint(imageGraphics);
		pageLoaded = true;
		if(!imagesLoaded) {
			imagesLoaded = ((CmsEditorKit) editor.getEditorKit()).getImageCount() == 0;
		}
		
		if( logger.isInfoEnabled())
			logger.info("page for generation preview was loaded");
	}

	private byte[] renderPreview(boolean isMailingPreview) {
		if(imagesLoaded && pageLoaded && !rendered) {
			if( logger.isInfoEnabled())
				logger.info("preview`s image rendering started...");

			// determine rendering page size
			Dimension preferredSize = editor.getPreferredSize();
			Dimension minimumSize = editor.getMinimumSize();
			Dimension currentSize = editor.getSize();
			if(minimumSize.width > currentSize.width ||
					minimumSize.height > currentSize.height) {
				currentSize.width = minimumSize.width;
				currentSize.height = minimumSize.height;
			} else if(preferredSize.width < currentSize.width) {
				currentSize.width = preferredSize.width;
				currentSize.height = preferredSize.height;
			} else if(preferredSize.height < currentSize.height) {
				currentSize.height = preferredSize.height;
			}

			// relayout page as we have now the new size and all images loaded
			editor.setSize(currentSize.width, currentSize.height);
			editor.addNotify();
			editor.validate();

			// paint page on image
			BufferedImage originalImage = new BufferedImage(currentSize.width,
					currentSize.height, BufferedImage.TYPE_INT_RGB);
			Graphics2D imageGraphics = originalImage.createGraphics();
			editor.paint(imageGraphics);

			// scale image
			Dimension previewSize = getPreviewSize(currentSize.width, currentSize.height, isMailingPreview);
			BufferedImage resultImage;
			if(previewSize.width > currentSize.width) {
				resultImage = originalImage;
			} else {
				BufferedImage croppedImage = cropImage(originalImage, currentSize.width, currentSize.height, isMailingPreview);
				Image scaledImage = croppedImage.getScaledInstance(previewSize.width, previewSize.height, Image.SCALE_SMOOTH);
				resultImage = new BufferedImage(previewSize.width, previewSize.height, BufferedImage.TYPE_INT_RGB);
				Graphics2D graphics = resultImage.createGraphics();
				graphics.drawImage(scaledImage, 0, 0, null);
				graphics.dispose();
			}

			try {
				ByteArrayOutputStream byteArrayOutputStream =
						new ByteArrayOutputStream();
				ImageIO.write(resultImage, "png", byteArrayOutputStream);

                byte[] imageData = byteArrayOutputStream.toByteArray();
                if (isMailingPreview) {
                    return imageData;
                } else {
                    storePreview(imageData);
                    return null;
                }
			} catch(IOException e) {
				logger.error("Error occurred while saving preview-image", e);
			}
			
			if( logger.isInfoEnabled())
				logger.info("preview`s image rendering finished");
			rendered = true;
		}
        return null;
	}

	private BufferedImage cropImage(BufferedImage originalImage, int width, int height, boolean mailingPreview) {
		if (!mailingPreview) return originalImage;
		double scaleX = ((double) previewMaxWidth) / ((double) width);
		double scaleY = ((double) previewMaxHeight) / ((double) height);
		int cropX = width - 1;
		int cropY = height - 1;
		if (scaleY > scaleX) {
			cropX = (int) (width * (scaleX / scaleY));
		}
		else {
			cropY = (int) (height * (scaleY / scaleX));
		}
		return originalImage.getSubimage(0, 0, cropX, cropY);
	}

	private void storePreview(byte[] imageData) {
		MediaFileManager mediaFileManager =
				CmsUtils.getMediaFileManager(aContext);
		MediaFile mediaFile =
				new MediaFile(cmTemplateId, 1, imageData, cmId, cmtId, 0,
						MediaFileUtils.PREVIEW_TYPE, "image/png", "preview");
		if(cmId != 0) {
			mediaFileManager.removePreviewOfContentModule(cmId);
		} else if(cmtId != 0) {
			mediaFileManager.removePreviewOfContentModuleType(cmtId);
		} else if(cmTemplateId != 0) {
			mediaFileManager.removePreviewOfContentModuleTemplate(cmTemplateId);
		}
		mediaFileManager.createMediaFile(mediaFile);
	}

	private Dimension getPreviewSize(int originalWidth, int originalHeight, boolean isMailingPreview) {
        if (isMailingPreview) return new Dimension(previewMaxWidth, previewMaxHeight);
		double scaleX = ((double) previewMaxWidth) / ((double) originalWidth);
		double scaleY = ((double) previewMaxHeight) / ((double) originalHeight);
		double scale = Math.min(scaleX, scaleY);
		return new Dimension((int) (scale * originalWidth), (int) (scale * originalHeight));
	}

	private String generatePreviewUrl(int cmTemplateId, int cmId, int cmtId) {
		String sessionId = session.getId();
		if(cmId > 0) {
			return "/cms_contentmodule.do;jsessionid=" + sessionId +
					"?action=" +
					ContentModuleAction.ACTION_PURE_PREVIEW +
					"&contentModuleId=" + cmId;
		} else if(cmTemplateId > 0) {
			return "/cms_cmtemplate.do;jsessionid=" + sessionId + "?action=" +
					CMTemplateAction.ACTION_PURE_PREVIEW + "&cmTemplateId=" +
					cmTemplateId;
		} else if(cmtId > 0) {
			return "/cms_cmt.do;jsessionid=" + sessionId + "?action=" +
					ContentModuleTypeAction.ACTION_PURE_PREVIEW + "&cmtId=" +
					cmtId;
		} else {
			return null;
		}
	}

}
